#ifndef RENDER_MANAGER_H
#define RENDER_MANAGER_H

#include "GLSL/GLSL_helper.h"
#include "MatrixStack/MStackHelp.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"

struct MeshInfo {
    GLuint indexBuffer, posBuffer, normalBuffer, textureBuffer;
    int vertexCount;
};

struct TransformData {
    float rotation;
    glm::vec3 rotationAxis;
    float scale;
    glm::vec3 translation;
};

struct RenderFlags {
    bool flipNormals, hasTextures;
};

class RenderManager {
    GLint h_aNormal;
    GLint h_aPosition;
    GLint h_uv;

    GLint h_uColor;

    GLint h_uNormalMatrix;
    GLint h_uModelMatrix;
    GLint h_uViewMatrix;
    GLint h_uProjMatrix;

    float rotation;

    //Matrix Stack
    RenderingHelper ModelTrans;

    bool uninitialized;

    private:
        void InitCheck() {
            if (uninitialized) {
                printf("UNINITIALIZED CALL ON RENDER_MANAGER\n");
            }
        }

    public:
        RenderManager() {
            rotation = 0.0f;
            uninitialized = true;
        }

        RenderManager(int shader) {
            rotation = 0.0f;
            uninitialized = false;

            ModelTrans.useModelViewMatrix();

            /* get handles to attribute data */
            h_aPosition = safe_glGetAttribLocation(shader, "aPosition");
            h_aNormal = safe_glGetAttribLocation(shader, "aNormal");
            h_uv = safe_glGetAttribLocation(shader, "uv");

            // Get Handles to Uniform data
            h_uColor = safe_glGetUniformLocation(shader, "uColor");
            h_uProjMatrix = safe_glGetUniformLocation(shader, "uProjMatrix");
            h_uViewMatrix = safe_glGetUniformLocation(shader, "uViewMatrix");
            h_uModelMatrix = safe_glGetUniformLocation(shader, "uModelMatrix");
            h_uNormalMatrix = safe_glGetUniformLocation(shader, "uNormalMatrix");
        }

        void SetModel() {
            safe_glUniformMatrix4fv(h_uModelMatrix, glm::value_ptr(ModelTrans.modelViewMatrix));
            safe_glUniformMatrix4fv(h_uNormalMatrix, glm::value_ptr(glm::transpose(glm::inverse(ModelTrans.modelViewMatrix))));
        }

        void TestModel(float x, float y, float z) {
            glm::mat4 indentity = glm::mat4(1.0f);

            indentity *= glm::translate(vec3(x, y, z));

            safe_glUniformMatrix4fv(h_uModelMatrix, glm::value_ptr(indentity));
            safe_glUniformMatrix4fv(h_uNormalMatrix, glm::value_ptr(glm::transpose(glm::inverse(indentity))));
        }

        /* projection matrix - do not change - sets matrix in shader */
        void SetProjectionMatrix(float windowWidth, float windowHeight) {
            InitCheck();
            glm::mat4 Projection = glm::perspective(80.0f, (float)windowWidth/windowHeight, 0.1f, 100.f);	
            safe_glUniformMatrix4fv(h_uProjMatrix, glm::value_ptr(Projection));
        }

        /* camera controls - do not change - sets matrix in shader */
        void SetView(glm::vec3 translate, float angle) {
            InitCheck();
            glm::mat4 op1 = glm::translate(glm::mat4(1.0f), translate);
            glm::mat4 op2 = glm::rotate(op1, angle, glm::vec3(0.0f, 1, 0));
            safe_glUniformMatrix4fv(h_uViewMatrix, glm::value_ptr(op2));
        }

        void StartStack() {
            ModelTrans.pushMatrix(); 
        }

        void EndStack() { 
            ModelTrans.popMatrix();
        }
    
        void Translate(glm::vec3 trans) {
            ModelTrans.translate(trans);
        }

        void Scale(float ratio) {
            ModelTrans.scale(ratio);
        }

        void Initialize() {
            ModelTrans.useModelViewMatrix();
        }
        
        void SetColor(vec3 color) {
            glUniform3f(h_uColor, color.x, color.y, color.z);
        }
        
        void DrawMesh(MeshInfo info, glm::vec3 translation, glm::vec3 scale, glm::vec3 rotateAxis, float angle) {
            ModelTrans.pushMatrix();
                //glUniform3f(h_uColor, 1.0, 1.0, 0.0);
                glm::mat4 m1 = glm::translate(glm::mat4(1.0f), translation);
                glm::mat4 m2 = glm::scale(m1, scale);
                glm::mat4 m3 = glm::rotate(m2, angle, rotateAxis);
                
                safe_glUniformMatrix4fv(h_uModelMatrix, glm::value_ptr(m3));
                safe_glUniformMatrix4fv(h_uNormalMatrix, glm::value_ptr(glm::transpose(glm::inverse(m3))));

                // Enable aPosition
                glEnableVertexAttribArray(h_aPosition);
                glBindBuffer(GL_ARRAY_BUFFER, info.posBuffer);
                glVertexAttribPointer(h_aPosition, 3, GL_FLOAT, GL_FALSE, 0, 0);

                // Enable aNormal
                glEnableVertexAttribArray(h_aNormal);
                glBindBuffer(GL_ARRAY_BUFFER, info.normalBuffer);
                glVertexAttribPointer(h_aNormal, 3, GL_FLOAT, GL_FALSE, 0, 0);
                
                // Enable UV
                glEnableVertexAttribArray(h_uv);
                glBindBuffer(GL_ARRAY_BUFFER, info.textureBuffer);
                glVertexAttribPointer(h_uv, 2, GL_FLOAT, GL_FALSE, 0, 0);

                // Read indicies and draw
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, info.indexBuffer);
                glDrawElements(GL_TRIANGLES, info.vertexCount, GL_UNSIGNED_INT, 0);
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

                // Disable the attributes
                safe_glDisableVertexAttribArray(h_aPosition);
                safe_glDisableVertexAttribArray(h_aNormal);
                safe_glDisableVertexAttribArray(h_uv);
            ModelTrans.popMatrix();
        }

        void DrawMesh(MeshInfo info, glm::vec3 translation, glm::vec3 scale) {
            ModelTrans.pushMatrix();
                //glUniform3f(h_uColor, 1.0, 1.0, 0.0);
                glm::mat4 m1 = glm::translate(glm::mat4(1.0f), translation);
                glm::mat4 m2 = glm::rotate(m1, rotation, glm::vec3(0.0f, 1.0f, 0.0f));
                glm::mat4 m3 = glm::scale(m2, scale);
                
                safe_glUniformMatrix4fv(h_uModelMatrix, glm::value_ptr(m3));
                safe_glUniformMatrix4fv(h_uNormalMatrix, glm::value_ptr(glm::transpose(glm::inverse(m3))));

                // Enable aPosition
                glEnableVertexAttribArray(h_aPosition);
                glBindBuffer(GL_ARRAY_BUFFER, info.posBuffer);
                glVertexAttribPointer(h_aPosition, 3, GL_FLOAT, GL_FALSE, 0, 0);

                // Enable aNormal
                glEnableVertexAttribArray(h_aNormal);
                glBindBuffer(GL_ARRAY_BUFFER, info.normalBuffer);
                glVertexAttribPointer(h_aNormal, 3, GL_FLOAT, GL_FALSE, 0, 0);
                
                // Enable UV
                glEnableVertexAttribArray(h_uv);
                glBindBuffer(GL_ARRAY_BUFFER, info.textureBuffer);
                glVertexAttribPointer(h_uv, 2, GL_FLOAT, GL_FALSE, 0, 0);

                // Read indicies and draw
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, info.indexBuffer);
                glDrawElements(GL_TRIANGLES, info.vertexCount, GL_UNSIGNED_INT, 0);
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

                // Disable the attributes
                safe_glDisableVertexAttribArray(h_aPosition);
                safe_glDisableVertexAttribArray(h_aNormal);
                safe_glDisableVertexAttribArray(h_uv);
            ModelTrans.popMatrix();
        }

        void LoadIdentity() {
            InitCheck();

            // Load the indentity matrix and set it
            ModelTrans.loadIdentity();
        }

        GLint GetColorHandle() {
            return h_uColor;
        }

        GLint GetNormalHandle() {
            return h_aNormal;
        }

        GLint GetPositionHandle() {
            return h_aPosition;
        }
};

#endif
